// 类型推导
// 显示指定类型
let num: number = 10;
// 声明后没有赋值，默认是any类型
let x;
x = 123;
x = '234';

// 变量声明时赋值，则以赋值类型为准，并且不能更改
let y = 234;
// 报错
// y = 'h1';

// 联合类型（指定类型中的一种）
let a: number | string;
// 当联合类型没有赋值的时候，我们只能访问联合类型共有的方法或者属性
// 可以试试在编译器中试试  console.log(a.toLocaleString());打点的时候会有提示
// 但是没有赋值，调用也会报错。空指针错误
// 当启用严格模式的时候，这里会报错，那么可以改为断言：console.log(a!.toLocaleString());代表a一定有值。
console.log(a.toLocaleString());

// 赋值为数字，调用数字的方法
a = 100;
console.log(a.toFixed(2));
// 赋值为字符串，就调用字符串的方法
a = 'hi';
a.charAt(1);

// HTMLElement TS 内置的元素类型。不写也会自动推导为这个类型
let obj: HTMLElement = document.getElementById('app');
// 严格模式下的断言叹号！
obj!.style.color = 'red';



/*********类型断言*********/
let b: number | string;
// 此处会报错
//b.toFixed(2);
// 强制转化为数字,此处为严格模式，非严格去掉叹号即可
(b! as number).toFixed(2);
// 另一种写法--但是不常用，因为与jsx语法类似。了解即可
(<number>b!).toFixed(2);

/************双重断言************/
let c: string | boolean;
// 尽量少用，因为any可以赋值给其他类型。
(c! as any) as string

/*************字面量类型***********/
// 代表season只能取其中的一个值，有点像枚举
let season: 'spring' | 'summer' | 'fall' | 'winter';
// 报错
// season = 'h1';
season = 'spring';
// 因为不能复用，还很长那么可以定义一下
type Season = 'spring' | 'summer' | 'fall' | 'winter';
// 利用字面量类型
let season2: Season = 'fall';



/*************函数类型*************/
// 限制函数的入参以及返回值类型。
function sum(a: number, b: number): number {
    return a + b;
}
// 函数表达式
let sum1 = function (a: number, b: number): number {
    return a + b;
}
// 相当于这样写
type SUM = (a: number, b: number) => number;
let sum2: SUM = function (b: number, c: number): number {
    return b + c;
}

// 默认参数可选参数
let sum3 = function (a: number, b: number = 10) {
    return a + b;
}
// b可以不填写，因为有默认值,也可以在指定类型的时候加一个问号，代表可选
console.log(sum3(20));

// 剩余参数
const sum4 = (...args: number[]): number => {
    return args.reduce((item, next) => item + next, 0);
}
console.log(sum4(1, 2, 3, 4));

// 函数重载---根据参数类型不同，返回值不同
// 参数为number按这个校验
function toArray(val: number): number[]
// 参数为string按这个来校验
function toArray(val: string): string[]
// 相当于上面两个是声明，这个是实现
function toArray(val: string | number) {
    if (typeof val === 'number') {
        return val.toString().split('').map(item => Number(item))
    } else {
        return val.split('');
    }
}
toArray(123);
toArray('456');


/***************类*************/
class Circle {
    // 实例上的属性，必须先声明，才能用
    x: number;
    y: number;
    round: number;

    constructor(x: number, y?: number, round: number = 10) {
        this.x = x;
        this.y = y!;
        this.round = round;
    }
}
let c1 = new Circle(10, 20, 5);

// 类的修饰符 public(默认) protected private readonly
// 修饰符放在constructor前面，也会有效果
class Person {
    // public 修饰的，父类、子类以及外面都能访问
    public name: string;
    // private修饰的只能自己访问
    private age: number;
    // 受保护的属性，自己和子类可以用，在外面是不能用的。
    protected high: number;
    // 被readonly修饰的在构造函数以外是不能被更改的，但是可以访问
    readonly phone;
    constructor(name: string, age: number,) {
        this.name = name;
        this.age = age;
        this.phone = '123';
    }

    // 静态属性
    static nation = 'china';
    // 静态方法
    static getName() {
        return "中国人"
    }

    eat() {
        console.log('吃饭');
    }
}
class Student extends Person {
    constructor(name: string, age: number) {
        super(name, age);
        console.log(this.high);
        // 报错
        // console.log(this.age);

    }
    // 原型方法
    study(): void {
        console.log('good good study');
    }
    // 设置私有属性
    private _score: number = 0;
    // 原型属性（大家都有的）
    get score() {
        return this._score;
    }
    // 原型属性的设置
    set score(newVal) {
        this._score = newVal;
    }
    // 子类重写父类的方法
    static getName() {
        return super.getName() + ",我很骄傲";
    }
    // 重写父类原型方法
    eat() {
        super.eat();
        console.log('再吃');
    }
}
let s1 = new Student('zs', 18);
console.log(s1.name);
// 报错，访问不到
// console.log(s1.age);
// 报错
// console.log(s1.high);
console.log(s1.phone);
// 访问静态属性
console.log(Person.nation);
// 访问静态方法
console.log(Person.getName());
// 静态方法，属性，也会被继承下来、通过new出来的实例是不具有的
// 如果不重写就继承，重写了就用自己的
console.log(Student.getName());


// 原型属性和方法
// 调用原型方法
s1.study();
// 访问原型方法
console.log(s1.score);
// 验证原型、里面有属性和方法
console.log(Student.prototype);
// 更改原型属性
s1.score = 90;
console.log(s1.score);



/**************super 关键字***************/
// 静态方法中的super指的是父类
// 原型方法中的super指的是父类的原型



/************抽象类****************/
// 抽象类不能被实例化，只能被继承
// 抽象方法没有具体实现，必须由子类去实现
// 抽象方法中的void返回值意思不是说只能void，而是不关心类型，返回其他的也OK，如果知道要返回什么，那就写具体的值

// 如果类不希望被实例化，就使用抽象类
abstract class Annimal {
    name!: string;

    // 抽象类中的方法可以有具体实现
    say() {
        console.log('say.....');
    }
    // 抽象方法 不提供实现，子类去实现
    abstract sleep(): void;
}
// 抽象类不能实例化（报错）只能被继承
// let animal = new Annimal();

class Cat extends Annimal {

    // 抽象方法必须被实现
    sleep(): void {
        console.log('趴着睡，喵');
    }
}
// 继承下来的可以实例化
let cat = new Cat();
// 继承抽象类的方法
cat.say();



/*************装饰器***************/
// 尽管用的不多，但是需要 知道。 VUE3很多用到
// 用来扩展属性或者方法，

/******** 类装饰器*******/
// 类装饰器在用的使用，要到tsconfig打开选项exprimentalDecorators 打开改成true
// 类装饰器中的形参是固定的，不能多，不能少
function addSay(target: any | Function) {
    // 装饰类的target指的是装饰的类本身
    console.log(target);// 实际上就是那个类
    // 可以随意操作。添加静态方法或者什么乱七八糟的
    target.prototype.say = function () {
        console.log('say.....');
    }
}
// 装饰属性的函数
function toUpper(target: any, key: string) {
    // 实例属性装饰器的target是类的原型，而不是类本身
    console.log(target);
    let val = '';
    Object.defineProperty(target, key, {
        get() {
            return val.toUpperCase();
        },
        set(newVal) {
            // 如果打印出来了张三，那说明装饰器先执行的，否则就是先赋值，后装饰器
            console.log(newVal);//结果也就是打印出来了，所以装饰器先行

            val = newVal;
        }
    })
}

/**
 * 实现数字 *3
 * @param target 类本身
 * @param key 类中的key
 */
function scale(target: any, key: string) {
    // 静态属性装饰器时。target是类本身
    console.log(target);
    // 获取到原来的值
    let value = target[key];

    Object.defineProperty(target, key, {
        get() {
            return value * 3;
        },
        set(newVal) {
            // 如果打印出来了张三，那说明装饰器先执行的，否则就是先赋值，后装饰器
            console.log(newVal);//结果也就是打印出来了，所以装饰器先行

            value = newVal;
        }
    })
}

// 不可枚举
function noEnum(target: any, key: String, descriptor: PropertyDescriptor) {
    console.log(target, key, descriptor);
    // key:方法名
    // descriptor：属性描述器
    // target：表示的是原型
    descriptor.enumerable = false;
}

function addPrifix(target: any, key: String, paramIndex: number) {
    console.log(target, key, paramIndex);
    // target :原型
    // key：函数名
    // paramIndex 修饰参数的索引
}

//多个装饰器，下面的先执行
@addSay
class DecPerson {
    eat!: Function;
    // 扩展设置的也要声明一下
    say!: Function;
    // 修饰类中的属性，把它转成大写
    @toUpper
    // 实例属性先装饰器，后赋值
    name: string = '张三';

    @scale
    // 静态属性先赋值，然后才走装饰器
    static age: number = 1;


    // 原型方法
    // 装饰器，使得方法不能枚举
    @noEnum
    getName(@addPrifix prifix: String) {
        return this.name;
    }
}

let per = new DecPerson();
per.say();
console.log(DecPerson.age);

// 装饰器的理解？
// 装饰器可以被附加到类、方法、属性或参数。可以修改类的行为
// 常见装饰器有：类装饰器、属性装饰器、方法装饰器、参数装饰器
// 装饰器可以分为普通装饰器和装饰器工厂（函数返回一个装饰器）
// 装饰器的执行顺序，越靠近越先执行，
// 多个参数装饰器的执行顺序是，后面的参数先执行
// 参数装饰器先执行，然后方法装饰器执行或者属性装饰器（谁在前面谁先执行），然后静态的属性或者方法，类装饰器总是最后执行，



/***************接口*************/
// 优先使用接口，不能用接口的时候，用type
// 接口与type有哪些不同？
// 接口可以被实现，可以被继承，type可以使用联合类型

// type FullName = {first:string,last:string};
// const fullname = ({first,last}:FullName):string =>{
//     return first + last;
// }

// 1、描述对象
// 将上面的改为接口
interface IFullName {
    first: string,
    last: string
}

// 描述函数
// 接口描述
interface IFn {
    // 函数声明
    (obj: IFullName): string
}
// 通过接口来限制函数的参数类型和返回值类型
const fullname: IFn = ({ first, last }: IFullName): string => {
    return first + last;
}


// 看下面的情况,用接口来修饰一下
// let fn = ()=>{}
// fn.num = 0;
// 混合描述
interface ICounter {
    (): number,//限制函数类型
    num: number//限制函数属性
}
let fn: any = () => {
    fn.num++;
    return fn.num;
}
// fn.num = 0;

let icount: ICounter = fn;
console.log(icount());
console.log(icount());



interface ICar {
    readonly color: string,// 只读属性
    weight: string
}

// 接口限制类，多个同名接口是会合并的。
const QQ: ICar = {
    color: 'black',
    weight: '50',
    // price:500, 是不能写的，因为接口没有声明，或者使用断言。把整个类转为ICar
}

// 强转
const BMW: ICar = {
    color: 'white',
    weight: '100',
    price: 400
} as ICar;



// 接口和抽象类的对比：
// 1、不同类之间共有的方法，或者属性，可以抽象成一个接口
// 2、抽象类不允许实例化，是其他类的基础类，供其他类继承
// 3、假如抽象类中有抽象方法，子类必须要实现
// 4、抽象类的本质是一个无法被实例化的类，其中能够实现共有的实现方法和初始化属性
// 5、接口仅仅能够用于描述，不能提供方法的实现，也不为属性初始化
// 6、类只可以继承一个类或者抽象类，但可以实现多个接口，抽象类可以实现接口
// 7、接口可以继承接口


/************范型**************/
class TAnimal {
    public age: number
    constructor(public name: string, age: number = 10) {
        // 两种声明方式
        this.name = name;
        this.age = age;
    }
}

class TPerson {
    constructor(public name: string) {
        this.name = name;
    }
}

// interface WithNameClass {
//     new (name: string): TAnimal
// }
//改为范型
interface WithNameClass<T> {
    new(name: string): T
}

//类名Animal 可以作为实例的类型。希望把clazz类型描述为构造函数
function createAnimal<T>(clazz: WithNameClass<T>, name: string): T {
    return new clazz(name);
}
//调用函数
let animal = createAnimal<TAnimal>(TAnimal, 'zhangsan');
let person = createAnimal(TPerson, 'zhangsan');
console.log(person.name);

//泛型、定义函数、接口或类的时候，不预先定义具体的类型，而是在使用的时候指定类型

// 1、单个泛型
// 常规写法
const getArr = (length: number, val: any): Array<any> => {
    let arr: any[] = [];
    for (let i = 0; i < length; i++) {
        arr[i] = val;
    }
    return arr;
}
let arr = getArr(3, 'a');
console.log(arr);//[a,a,a]

// 改为泛型写法（泛型函数）
const TgetArr = <T>(length: number, val: T): Array<T> => {
    let arr: T[] = [];
    for (let i = 0; i < length; i++) {
        arr[i] = val;
    }
    return arr;
}
// 这样做的保证是传入什么类型，返回就是什么类型
let tarr = TgetArr<number>(2, 12);
let tarr2 = TgetArr<string>(2, 'str');

// 2、多个泛型
function swap<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]];
}
// 类型可以自动推断
const r = swap([1, 2]);
// 显式指定类型
const r2 = swap<number, string>([1, '2']);
const r3 = swap(['1', '2']);

interface Swap{
    <T, U>(tuple: [T, U]): [U, T]
}
type SwapTYpe = <T,U>(tuple:[T,U]) => [U,T];
// 用接口或者type来定义类型
// 联合类型只能用type，其他能用接口就接口
const swap1:Swap|SwapTYpe = <T, U>(tuple: [T, U]): [U, T] => {
    return [tuple[1], tuple[0]];
}


// 3、接口泛型
interface ISum<U>{
    (a:U,b:U): U
}
let tsum:ISum<number> = (a:number,b:number):number =>{
    return a+b;
}
interface ISum2{
    <T>(a:T,b:T): T
}
let tsum2:ISum2 = <T>(a,b):T =>{
    return a+b;
}
tsum2<string>('123','qwe');

// 4、默认泛型
interface T2<T = string>{
    // 默认string，传了就以传的为准
    name:T
}
let name1:T2<number> = {name:123}

// 5 函数范型与接口范型
// 函数范型
interface Calculate{
    <T>(a:T,b:T):T
}
const cal:Calculate = <T>(a:T,b:T):T=>a;
cal<number>(1,2);
// 接口范型
interface Calculate1<T>{
    (a:T,b:T):T
}
const cal1:Calculate1<number> = (a: number, b: number):number => a;
cal1(1,2);
// cal1('1','2');
// 接口+ 函数范型
interface Calculate3<T>{
    <U> (a:T,b:T) :U;
}
const cal3:Calculate3<number> = <U>(a:number,b:number):U => a as any
cal3<string>(1,2);

// 6、范型类
class MyArray<T>{
    private list:T[] = [];
    add(val:T){
        this.list.push(val);
    }
    /**
     * 返回最大值
     * @returns 返回最大值
     */
    getMax():T{
        let max = this.list[0];
        for(let i = 0 ;i<this.list.length; i++){
            if(this.list[i]> max){
                max = this.list[i];
            }
        }
        return max;
    }
}
let myarr = new MyArray<number>();
myarr.add(3);
myarr.add(4);
myarr.getMax();

// 7、范型与new
// 描述构造函数
function factory<T>(clazz: new()=>T) {
    return new clazz();
}
class A{}
console.log(factory<A>(A));


// 范型约束
function logger<T>(val:T) {
    // 这里会直接报错
    // console.log(val.length);
}
// 约束起来
interface LengthWise{
    length:number
}
// 这里的extends相当于是约束
function logger2<T extends LengthWise>(val:T){
    console.log(val.length);
}
console.log(logger2<string>('zx'));
// 符合约束，但是多余属性不要紧，但是不能少
const logobj = {length:10,num:1}
console.log(logger2(logobj));

